package etcd

import (
	"context"
	"encoding/json"
	etcdV3 "go.etcd.io/etcd/client/v3"
)

type Watcher struct {
	key         string
	ctx         context.Context
	cancel      context.CancelFunc
	watchChan   etcdV3.WatchChan
	watcher     etcdV3.Watcher
	kv          etcdV3.KV
	first       bool
	serviceName string
}

func newWatcher(ctx context.Context, key, name string, client *etcdV3.Client) (*Watcher, error) {
	w := &Watcher{
		key:         key,
		watcher:     etcdV3.NewWatcher(client),
		kv:          etcdV3.NewKV(client),
		first:       true,
		serviceName: name,
	}
	w.ctx, w.cancel = context.WithCancel(ctx)
	w.watchChan = w.watcher.Watch(w.ctx, key, etcdV3.WithPrefix(), etcdV3.WithRev(0), etcdV3.WithKeysOnly())
	err := w.watcher.RequestProgress(context.Background())
	if err != nil {
		return nil, err
	}
	return w, nil
}

func (w *Watcher) Next() ([]*ServiceInstance, error) {
	if w.first {
		item, err := w.getInstance()
		w.first = false
		return item, err
	}

	select {
	case <-w.ctx.Done():
		return nil, w.ctx.Err()
	case <-w.watchChan:
		return w.getInstance()
	}
}

func (w *Watcher) Stop() error {
	w.cancel()
	return w.watcher.Close()
}

func (w *Watcher) getInstance() ([]*ServiceInstance, error) {
	resp, err := w.kv.Get(w.ctx, w.key, etcdV3.WithPrefix())
	if err != nil {
		return nil, err
	}
	items := make([]*ServiceInstance, 0, len(resp.Kvs))
	for _, kv := range resp.Kvs {
		instance := new(ServiceInstance)
		err = json.Unmarshal(kv.Value, instance)
		if err != nil {
			return nil, err
		}
		if instance.Name != w.serviceName {
			continue
		}
		items = append(items, instance)
	}
	return items, nil
}

func (w *Watcher) First() bool {
	return w.first
}
